package com.cee.admin.config.idempotent;

import com.cee.admin.log.util.ServletUtils;
import com.cee.cache.redis.RedisUtils;
import com.cee.common.annotations.Idempotent;
import com.cee.common.exception.GlobalException;
import com.cee.common.utils.StringUtils;
import com.cee.common.web.result.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author wsz
 * @since 2024/3/11
 */
@Slf4j
@Aspect
@Component
public class IdempotentAspect {

    private static final ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<>();


    private static final String IDEMPOTENT_HEADER = "idempotent";

    private static final String KEY_PREFIX = "idempotent";


    private static final String KEY = "key";

    private static final String DEL_KEY = "delKey";

    @Autowired
    private RedisUtils redisUtils;


    @Pointcut("@annotation(com.cee.common.annotations.Idempotent)")
    public void pointCut() {
    }

    @Before("pointCut()")
    public void beforePointCut(JoinPoint joinPoint) {
        HttpServletRequest request = ServletUtils.getRequest();
        if (request == null) {
            return;
        }
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        if (!method.isAnnotationPresent(Idempotent.class)) {
            return;
        }
        Idempotent idempotent = method.getAnnotation(Idempotent.class);
        boolean isIdempotent = idempotent.isIdempotent();
        if (!isIdempotent) {
            return;
        }
        long expireTime = idempotent.expireTime();
        String info = idempotent.info();
        TimeUnit timeUnit = idempotent.timeUnit();
        boolean delKey = idempotent.delKey();

        String header = request.getHeader(IDEMPOTENT_HEADER);
        if (StringUtils.isBlank(header)) {
            throw new GlobalException(ResultCode.PARAM_NOT_COMPLETE);
        }

        String key = KEY_PREFIX + header;
        boolean setSuccess = redisUtils.setIfAbsent(header, System.currentTimeMillis(), timeUnit.toSeconds(expireTime));

        if (setSuccess) {
            Map<String, Object> map =
                    CollectionUtils.isEmpty(threadLocal.get()) ? new HashMap<>(4) : threadLocal.get();
            map.put(KEY, key);
            map.put(DEL_KEY, delKey);
            threadLocal.set(map);
        } else {
            throw new GlobalException(info);
        }
    }

    @After("pointCut()")
    public void afterPointCut() {
        Map<String, Object> map = threadLocal.get();
        if (CollectionUtils.isEmpty(map)) {
            return;
        }
        try {
            String key = map.get(KEY).toString();
            boolean delKey = (boolean) map.get(DEL_KEY);

            if (delKey) {
                redisUtils.del(key);
                log.info("[idempotent]:has removed key={}", key);
            }
        } finally {
            threadLocal.remove();
        }
    }
}
